use std::fs;
use std::io;

fn read_input() -> Vec<Vec<char>> {
    let parser = |s: &str| -> Vec<char> {
        s.chars().collect()
    };
    let input = fs::read_to_string("./day03/input.txt").unwrap();
    input.lines().into_iter().map(|line| parser(line)).collect()
}

#[allow(unused)]
fn part1(numbers: & Vec<Vec<char>>) {
    let length = numbers[0].len();

    let mut gamma_rate = "".to_string();
    let mut epsilon_rate = "".to_string();

    for col in 0..length {
        let mut zero_count: usize = 0;
        let mut one_count: usize = 0;
        for row in 0..(numbers.len()) {
            if numbers[row][col] == '0' {
                zero_count += 1;
            } else {
                one_count += 1;
            }
        }

        if zero_count > one_count {
            gamma_rate.push('0');
            epsilon_rate.push('1');
        } else {
            gamma_rate.push('1');
            epsilon_rate.push('0');
        }
    }

    let gamma_rate = i32::from_str_radix(&gamma_rate, 2).unwrap();
    let epsilon_rate = i32::from_str_radix(&epsilon_rate, 2).unwrap();

    println!("{}", gamma_rate * epsilon_rate);
}

#[allow(unused)]
fn part2(numbers: &Vec<Vec<char>>) {

    fn count_zero_and_one(numbers: &Vec<Vec<char>>, col: usize) -> (usize, usize) {
        let mut zero_count: usize = 0;
        let mut one_count: usize = 0;
        for row in 0..(numbers.len()) {
            if numbers[row][col] == '0' {
                zero_count += 1;
            } else {
                one_count += 1;
            }
        }

        (zero_count, one_count)
    }

    fn filter_oxygen_numbers(numbers: Vec<Vec<char>>, col: usize) -> Vec<Vec<char>> {
        let (zero_count, one_count) = count_zero_and_one(&numbers, col);
        let left_char = match zero_count.cmp(&one_count) {
            std::cmp::Ordering::Equal => '1',
            std::cmp::Ordering::Greater => '0',
            std::cmp::Ordering::Less => '1',
        };
        numbers.into_iter().filter(|number| number[col] == left_char).collect()
    }

    fn filter_co2_numbers(numbers: Vec<Vec<char>>, col: usize) -> Vec<Vec<char>> {
        let (zero_count, one_count) = count_zero_and_one(&numbers, col);
        let left_char = match zero_count.cmp(&one_count) {
            std::cmp::Ordering::Equal => '0',
            std::cmp::Ordering::Greater => '1',
            std::cmp::Ordering::Less => '0',
        };
        numbers.into_iter().filter(|number| number[col] == left_char).collect()
    }

    type Filter = fn(Vec<Vec<char>>, usize) -> Vec<Vec<char>>;
    fn get_generator_rating(numbers: Vec<Vec<char>>, filter: Filter) -> i32 {
        fn do_get_generator_rating(numbers: Vec<Vec<char>>, col: usize, filter: Filter) -> i32 {
            let numbers = filter(numbers, col);
            if numbers.len() == 1 {
                let s: String = numbers[0].iter().collect();
                i32::from_str_radix( &s, 2).unwrap()
            } else {
                do_get_generator_rating(numbers, col + 1, filter)
            }
        } 

        do_get_generator_rating(numbers, 0, filter)
    }

    let oxygen = get_generator_rating(numbers.clone(), filter_oxygen_numbers);
    let co2 = get_generator_rating(numbers.clone(), filter_co2_numbers);

    println!("{}", oxygen * co2);
}

fn main() -> io::Result<()> {
    let numbers = read_input();

    // part1(&numbers);
    part2(&numbers);

    Ok(())
}
